﻿using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.Windows.Design.Metadata;
using Microsoft.Windows.Design.PropertyEditing;

namespace DenisVuyka.Controls.PropertyGrid
{
  using Data;    
  using Metadata;
using System.ComponentModel;
  using System.Diagnostics;
    
  /// <summary>
  /// PropertyGrid control.
  /// </summary>
  public class PropertyGrid : Control
  {
    #region Static fields
    /// <summary>
    /// Identifies the empty property filter.
    /// </summary>
    public static readonly string EmptyPropertyFilter = string.Empty; 
    #endregion

    #region ctor
    static PropertyGrid()
    {
      DefaultStyleKeyProperty.OverrideMetadata(typeof(PropertyGrid), new FrameworkPropertyMetadata(typeof(PropertyGrid)));

      // Register default metadata
      MetadataStore.AddAttributeTable(new DefaultMetadataBuilder().CreateTable());
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="PropertyGrid"/> class.
    /// </summary>
    public PropertyGrid()
    {
      Resources.MergedDictionaries.Add(new Themes.Black());

      this.CommandBindings.Add(new CommandBinding(PropertyGridCommands.ResetFilter, OnResetFilterCommand));
      this.CommandBindings.Add(new CommandBinding(PropertyGridCommands.Reload, OnReloadCommand));
      this.CommandBindings.Add(new CommandBinding(PropertyGridCommands.ShowReadOnlyProperties, OnShowReadOnlyPropertiesCommand));
      this.CommandBindings.Add(new CommandBinding(PropertyGridCommands.HideReadOnlyProperties, OnHideReadOnlyPropertiesCommand));
      this.CommandBindings.Add(new CommandBinding(PropertyGridCommands.ToggleReadOnlyProperties, OnToggleReadOnlyPropertiesCommand));
      this.CommandBindings.Add(new CommandBinding(PropertyGridCommands.ShowAttachedProperties, OnShowAttachedPropertiesCommand));
      this.CommandBindings.Add(new CommandBinding(PropertyGridCommands.HideAttachedProperties, OnHideAttachedPropertiesCommand));
      this.CommandBindings.Add(new CommandBinding(PropertyGridCommands.ToggleAttachedProperties, OnToggleAttachedPropertiesCommand));
      this.CommandBindings.Add(new CommandBinding(PropertyGridCommands.ShowFilter, OnShowFilterCommand));
      this.CommandBindings.Add(new CommandBinding(PropertyGridCommands.HideFilter, OnHideFilterCommand));
      this.CommandBindings.Add(new CommandBinding(PropertyGridCommands.ToggleFilter, OnToggleFilterCommand));
      this.CommandBindings.Add(new CommandBinding(PropertyValueEditorCommands.ShowDialogEditor, OnShowDialog));
    } 
    #endregion

    #region Events

    #region PropertyValueChanged event (Bubble)
    /// <summary>
    /// Identifies the <see cref="PropertyValueChanged"/> routed event.
    /// </summary>
    public static readonly RoutedEvent PropertyValueChangedEvent =
      EventManager.RegisterRoutedEvent("PropertyValueChanged", RoutingStrategy.Bubble, typeof(PropertyValueChangedEventHandler), typeof(PropertyGrid));

    /// <summary>
    /// Occurs when property item value is changed.
    /// </summary>
    public event PropertyValueChangedEventHandler PropertyValueChanged
    {
      add { AddHandler(PropertyValueChangedEvent, value); }
      remove { RemoveHandler(PropertyValueChangedEvent, value); }
    }

    private void RaisePropertyValueChangedEvent(PropertyItem property, object oldValue)
    {
      PropertyValueChangedEventArgs args = new PropertyValueChangedEventArgs(PropertyValueChangedEvent, property, oldValue);
      RaiseEvent(args);
    } 
    #endregion

    #region PreviewPropertyCreated event (Tunnel)
    /// <summary>
    /// Identifies the <see cref="PreviewPropertyCreated"/> routed event.
    /// </summary>
    public static readonly RoutedEvent PreviewPropertyCreatedEvent =
      EventManager.RegisterRoutedEvent("PreviewPropertyCreated", RoutingStrategy.Tunnel, typeof(PropertyDescriptorEventHandler), typeof(PropertyGrid));

    /// <summary>
    /// Occurs before the property is created.
    /// </summary>
    public event PropertyDescriptorEventHandler PreviewPropertyCreated
    {
      add { AddHandler(PreviewPropertyCreatedEvent, value); }
      remove { RemoveHandler(PreviewPropertyCreatedEvent, value); }
    }

    /// <summary>
    /// Raises the <see cref="E:PreviewPropertyCreated"/> event.
    /// </summary>
    /// <param name="e">The <see cref="DenisVuyka.Controls.PropertyGrid.PropertyDescriptorEventArgs"/> instance containing the event data.</param>
    protected virtual void OnPreviewPropertyCreated(PropertyDescriptorEventArgs e)
    {
      RaiseEvent(e);
    } 
    #endregion

    #region PropertyCreated event (Bubble)
    /// <summary>
    /// Identifies the <see cref="PropertyCreated"/> routed event.
    /// </summary>
    public static readonly RoutedEvent PropertyCreatedEvent =
      EventManager.RegisterRoutedEvent("PropertyCreated", RoutingStrategy.Bubble, typeof(PropertyItemEventHandler), typeof(PropertyGrid));

    /// <summary>
    /// Occurs after the property was created.
    /// </summary>
    public event PropertyItemEventHandler PropertyCreated
    {
      add { AddHandler(PropertyCreatedEvent, value); }
      remove { RemoveHandler(PropertyCreatedEvent, value); }
    }

    /// <summary>
    /// Raises the <see cref="E:PropertyCreated"/> event.
    /// </summary>
    /// <param name="e">The <see cref="DenisVuyka.Controls.PropertyGrid.PropertyItemEventArgs"/> instance containing the event data.</param>
    protected virtual void OnPropertyCreated(PropertyItemEventArgs e)
    {
      RaiseEvent(e);
    } 
    #endregion

    #region PreviewCategoryCreated event (Tunnel)
    /// <summary>
    /// Identifies the <see cref="PreviewCategoryCreated"/> routed event.
    /// </summary>
    public static readonly RoutedEvent PreviewCategoryCreatedEvent =
      EventManager.RegisterRoutedEvent("PreviewCategoryCreated", RoutingStrategy.Tunnel, typeof(CategoryEventHandler), typeof(PropertyGrid));

    /// <summary>
    /// Occurs before a category is created.
    /// </summary>
    public event CategoryEventHandler PreviewCategoryCreated
    {
      add { AddHandler(PreviewCategoryCreatedEvent, value); }
      remove { RemoveHandler(PreviewCategoryCreatedEvent, value); }
    }

    /// <summary>
    /// Raises the <see cref="E:PreviewCategoryCreated"/> event.
    /// </summary>
    /// <param name="e">The <see cref="DenisVuyka.Controls.PropertyGrid.CategoryEventArgs"/> instance containing the event data.</param>
    protected virtual void OnPreviewCategoryCreated(CategoryEventArgs e)
    {
      RaiseEvent(e);
    } 
    #endregion

    #region CategoryCreated event (Bubble)
    /// <summary>
    /// Identifies the <see cref="CategoryCreated"/> routed event.
    /// </summary>
    public static readonly RoutedEvent CategoryCreatedEvent =
      EventManager.RegisterRoutedEvent("CategoryCreated", RoutingStrategy.Bubble, typeof(CategoryItemEventHandler), typeof(PropertyGrid));

    /// <summary>
    /// Occurs after a category was created.
    /// </summary>
    public event CategoryItemEventHandler CategoryCreated
    {
      add { AddHandler(CategoryCreatedEvent, value); }
      remove { RemoveHandler(CategoryCreatedEvent, value); }
    }

    /// <summary>
    /// Raises the <see cref="E:CategoryCreated"/> event.
    /// </summary>
    /// <param name="e">The <see cref="DenisVuyka.Controls.PropertyGrid.CategoryItemEventArgs"/> instance containing the event data.</param>
    protected virtual void OnCategoryCreated(CategoryItemEventArgs e)
    {
      RaiseEvent(e);
    } 
    #endregion

    #endregion

    #region Properties

    #region SelectedObject Property

    /// <summary>
    /// Identifies the <see cref="SelectedObject"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty SelectedObjectProperty =
      DependencyProperty.Register("SelectedObject", typeof(object), typeof(PropertyGrid),
      new FrameworkPropertyMetadata(null, OnSelectedObjectChanged, CoerceSelectedObject));

    /// <summary>
    /// Object for editing properties. This is a dependency property.
    /// </summary>
    /// <value>The selected object.</value>
    public object SelectedObject
    {
      get { return (object)GetValue(SelectedObjectProperty); }
      set { SetValue(SelectedObjectProperty, value); }
    }

    /// <summary>
    /// Invoked on Instance change.
    /// </summary>
    /// <param name="d">The object that was changed</param>
    /// <param name="e">Dependency property changed event arguments</param>
    static void OnSelectedObjectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      PropertyGrid propertyGrid = d as PropertyGrid;
      propertyGrid.DoReload();     
    }

    static object CoerceSelectedObject(DependencyObject d, object value)
    {
      PropertyGrid propertyGrid = d as PropertyGrid;
      if (value == null)
        return propertyGrid.FallbackValue;

      return value;
    }

    #endregion

    #region FallbackValue

    /// <summary>
    /// Identifies the <see cref="FallbackValue"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty FallbackValueProperty =
      DependencyProperty.Register("FallbackValue", typeof(object), typeof(PropertyGrid),
      new FrameworkPropertyMetadata(null, OnFallbackValueChanged));

    /// <summary>
    /// Gets or sets the fallback value.
    /// </summary>
    /// <value>The fallback value.</value>
    public object FallbackValue
    {
      get { return GetValue(FallbackValueProperty); }
      set { SetValue(FallbackValueProperty, value); }
    }

    /// <summary>
    /// Called when FallbackValue property changed.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
    static void OnFallbackValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
    }
    #endregion

    #region Properties property
    private static readonly DependencyPropertyKey PropertiesPropertyKey =
      DependencyProperty.RegisterReadOnly("Properties", typeof(ObservableCollection<PropertyItem>), typeof(PropertyGrid),
      new FrameworkPropertyMetadata(new ObservableCollection<PropertyItem>(), OnPropertiesPropertyChanged));

    /// <summary>
    /// Identifies the <see cref="Properties"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty PropertiesProperty = PropertiesPropertyKey.DependencyProperty;

    /// <summary>
    /// Gets the properties. This is a dependency property.
    /// </summary>
    /// <value>The properties.</value>
    public ObservableCollection<PropertyItem> Properties
    {
      get { return (ObservableCollection<PropertyItem>)GetValue(PropertiesProperty); }
      private set { SetValue(PropertiesPropertyKey, value); }
    }

    private static void OnPropertiesPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
      PropertyGrid propertyGrid = sender as PropertyGrid;
      ObservableCollection<PropertyItem> oldItems = e.OldValue as ObservableCollection<PropertyItem>;

      foreach (PropertyItem item in oldItems)
      {
        propertyGrid.UnhookPropertyChanged(item);
        item.Dispose();
      }

      ObservableCollection<PropertyItem> newItems = e.NewValue as ObservableCollection<PropertyItem>;
      if (newItems != null)
        foreach (PropertyItem item in newItems)
          propertyGrid.HookPropertyChanged(item);
    }    
    #endregion

    #region CategoryComparer
    private IComparer<CategoryEntry> categoryComparer;

    /// <summary>
    /// Gets or sets the default category comparer.
    /// </summary>
    /// <value>The default category comparer.</value>
    public IComparer<CategoryEntry> CategoryComparer
    {
      get
      {
        if (categoryComparer == null) categoryComparer = new CategoryEntryComparer();
        return categoryComparer;
      }
      set { categoryComparer = value; }
    } 
    #endregion

    #region Categories Property
    
    private static readonly DependencyPropertyKey CategoriesPropertyKey = 
      DependencyProperty.RegisterReadOnly("Categories", typeof(ObservableList<CategoryEntry>), typeof(PropertyGrid),
      new FrameworkPropertyMetadata(new ObservableList<CategoryEntry>(), OnCategoriesPropertyChanged));

    /// <summary>
    /// Identifies the <see cref="Categories"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty CategoriesProperty = CategoriesPropertyKey.DependencyProperty;

    /// <summary>
    /// Gets the categories. This is a dependency property.
    /// </summary>
    /// <value>The categories.</value>
    public ObservableList<CategoryEntry> Categories
    {
      get { return (ObservableList<CategoryEntry>)GetValue(CategoriesProperty); }
      private set { SetValue(CategoriesPropertyKey, value); }
    }

    private static void OnCategoriesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      PropertyGrid propertyGrid = d as PropertyGrid;
      ObservableList<CategoryEntry> categories = (ObservableList<CategoryEntry>)e.NewValue;
      if (categories != null)
        categories.Sort(propertyGrid.CategoryComparer);
    }
        
    #endregion
       
    #region ShowReadOnlyProperties property

    /// <summary>
    /// Identifies the <see cref="ShowReadOnlyProperties"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty ShowReadOnlyPropertiesProperty =
     DependencyProperty.Register("ShowReadOnlyProperties", typeof(bool), typeof(PropertyGrid), 
     new PropertyMetadata(false, OnShowReadOnlyPropertiesChanged));

    private static void OnShowReadOnlyPropertiesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
      PropertyGrid pg = (PropertyGrid)sender;
      if (pg.Categories.Count > 0 | pg.Properties.Count > 0)
        pg.DoReload();
    }

    /// <summary>
    /// Gets or sets a value indicating whether read-only properties should be displayed. This is a dependency property.
    /// </summary>
    /// <value>
    /// 	<c>true</c> if read-only properties should be displayed; otherwise, <c>false</c>. Default is <c>false</c>.
    /// </value>
    public bool ShowReadOnlyProperties
    {
      get { return (bool)GetValue(ShowReadOnlyPropertiesProperty); }
      set { SetValue(ShowReadOnlyPropertiesProperty, value); }
    }

    #endregion

    #region ShowAttachedProperties property

    /// <summary>
    /// Identifies the <see cref="ShowAttachedProperties"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty ShowAttachedPropertiesProperty =
      DependencyProperty.Register("ShowAttachedProperties", typeof(bool), typeof(PropertyGrid), 
      new PropertyMetadata(false, OnShowAttachedPropertiesChanged));

    private static void OnShowAttachedPropertiesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
      PropertyGrid pg = (PropertyGrid)sender;
      if (pg.Categories.Count > 0 | pg.Properties.Count > 0)
        pg.DoReload();
    }

    /// <summary>
    /// Gets or sets a value indicating whether attached properties should be displayed.
    /// </summary>
    /// <value>
    /// 	<c>true</c> if attached properties should be displayed; otherwise, <c>false</c>. Default is <c>false</c>.
    /// </value>
    public bool ShowAttachedProperties
    {
      get { return (bool)GetValue(ShowAttachedPropertiesProperty); }
      set { SetValue(ShowAttachedPropertiesProperty, value); }
    }

    #endregion

    #region PropertyFilter
    /// <summary>
    /// Identifies the <see cref="PropertyFilter"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty PropertyFilterProperty =
      DependencyProperty.Register("PropertyFilter", typeof(string), typeof(PropertyGrid), 
      new FrameworkPropertyMetadata(EmptyPropertyFilter, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnPropertyFilterChanged)));

    private static void OnPropertyFilterChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
      PropertyGrid propertyGrid = (PropertyGrid)sender;
      foreach (CategoryItem category in propertyGrid.Categories)
        category.ApplyFilter(new PropertyFilter(propertyGrid.PropertyFilter));
    }

    /// <summary>
    /// Gets or sets the property filter. This is a dependency property.
    /// </summary>
    /// <value>The property filter.</value>
    public string PropertyFilter
    {
      get { return (string)GetValue(PropertyFilterProperty); }
      set { SetValue(PropertyFilterProperty, value); }
    } 
    #endregion

    #region ShowPropertyFilter
    /// <summary>
    /// Identifies the <see cref="ShowPropertyFilter"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty ShowPropertyFilterProperty =
      DependencyProperty.Register("ShowPropertyFilter", typeof(bool), typeof(PropertyGrid), 
      new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnShowPropertyFilterChanged)));

    private static void OnShowPropertyFilterChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
      if (!(bool)e.NewValue)
      {
        PropertyGrid propertyGrid = (PropertyGrid)sender;
        propertyGrid.DoResetFilter();
      }
    }

    /// <summary>
    /// Gets or sets a value indicating whether to show property filter. This is a dependency property.
    /// </summary>
    /// <value>
    ///   <c>true</c> if property filter should be displayed; otherwise, <c>false</c>. Default is <c>true</c>.
    /// </value>
    public bool ShowPropertyFilter
    {
      get { return (bool)GetValue(ShowPropertyFilterProperty); }
      set { SetValue(ShowPropertyFilterProperty, value); }
    } 
    #endregion

    #region PropertyDisplayMode

    /// <summary>
    /// Identifies the <see cref="PropertyDisplayMode"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty PropertyDisplayModeProperty =
      DependencyProperty.Register("PropertyDisplayMode", typeof(PropertyDisplayModes), typeof(PropertyGrid),
      new FrameworkPropertyMetadata(PropertyDisplayModes.All, new PropertyChangedCallback(OnPropertyDisplayModePropertyChanged)));

    /// <summary>
    /// Gets or sets the property display mode. This is a dependency property.
    /// </summary>
    /// <value>The property display mode.</value>
    public PropertyDisplayModes PropertyDisplayMode
    {
      get { return (PropertyDisplayModes)GetValue(PropertyDisplayModeProperty); }
      set { SetValue(PropertyDisplayModeProperty, value); }
    }

    private static void OnPropertyDisplayModePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
      PropertyGrid propertyGrid = (PropertyGrid)sender;
      propertyGrid.DoReload();
    }

    #endregion

    #region PropertyDisplaySchema

    /// <summary>
    /// Identifies the <see cref="PropertyDisplaySchema"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty PropertyDisplaySchemaProperty =
      DependencyProperty.Register("PropertyDisplaySchema", typeof(PropertyDisplaySchema), typeof(PropertyGrid),
      new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnPropertyDisplaySchemaPropertyChanged)));

    /// <summary>
    /// Gets or sets the property display schema. This is a dependency property.
    /// </summary>
    /// <value>The property display schema.</value>
    public PropertyDisplaySchema PropertyDisplaySchema
    {
      get { return (PropertyDisplaySchema)GetValue(PropertyDisplaySchemaProperty); }
      set { SetValue(PropertyDisplaySchemaProperty, value); }
    }

    private static void OnPropertyDisplaySchemaPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
      PropertyGrid propertyGrid = (PropertyGrid)sender;
      propertyGrid.DoReload();
    }

    #endregion

    #endregion

    #region Commands
    
    private void OnResetFilterCommand(object sender, ExecutedRoutedEventArgs e)
    {
      DoResetFilter();      
    }

    private void OnReloadCommand(object sender, ExecutedRoutedEventArgs e)
    {
      DoReload();
    }

    private void OnShowReadOnlyPropertiesCommand(object sender, ExecutedRoutedEventArgs e)
    {
      ShowReadOnlyProperties = true;
    }

    private void OnHideReadOnlyPropertiesCommand(object sender, ExecutedRoutedEventArgs e)
    {
      ShowReadOnlyProperties = false;
    }

    private void OnToggleReadOnlyPropertiesCommand(object sender, ExecutedRoutedEventArgs e)
    {
      ShowReadOnlyProperties = !ShowReadOnlyProperties;
    }

    private void OnShowAttachedPropertiesCommand(object sender, ExecutedRoutedEventArgs e)
    {
      ShowAttachedProperties = true;
    }

    private void OnHideAttachedPropertiesCommand(object sender, ExecutedRoutedEventArgs e)
    {
      ShowAttachedProperties = false;
    }

    private void OnToggleAttachedPropertiesCommand(object sender, ExecutedRoutedEventArgs e)
    {
      ShowAttachedProperties = !ShowAttachedProperties;
    }

    private void OnShowFilterCommand(object sender, ExecutedRoutedEventArgs e)
    {
      ShowPropertyFilter = true;
    }

    private void OnHideFilterCommand(object sender, ExecutedRoutedEventArgs e)
    {
      ShowPropertyFilter = false;
    }

    private void OnToggleFilterCommand(object sender, ExecutedRoutedEventArgs e)
    {
      ShowPropertyFilter = !ShowPropertyFilter;
    }

    // TODO: refactoring needed
    private void OnShowDialog(object sender, ExecutedRoutedEventArgs e)
    {
      EditModeSwitchButton btn = (e.OriginalSource as EditModeSwitchButton);
      if (btn == null) return;
      PropertyItemValue value = btn.DataContext as PropertyItemValue;
      if (value == null) return;
      PropertyItem property = value.ParentProperty as PropertyItem;
      if (property == null) return;
      DialogPropertyValueEditor dialog = property.PropertyValueEditor as DialogPropertyValueEditor;
      if (dialog == null) return;
      dialog.ShowDialog(value, this);
    }
    #endregion

    #region Private members

    private void DoResetFilter()
    {
      this.PropertyFilter = EmptyPropertyFilter;
    }
        
    private void DoReload()
    {
      if (SelectedObject == null)
      {
        Categories = new ObservableList<CategoryEntry>();
        Properties = new ObservableCollection<PropertyItem>();
      }
      else
      {
        CategoryItemCollection categories = new CategoryItemCollection(this, SelectedObject);
        this.Categories = categories.Categories;
        this.Properties = categories.Items;
      }
    }

    private void OnPropertyItemValueChanged(PropertyItem property, object oldValue, object newValue)
    {
      RaisePropertyValueChangedEvent(property, oldValue);
    }

    private void HookPropertyChanged(PropertyItem item)
    {
      PropertyItem property = item as PropertyItem;
      if (property == null) return;
      property.ValueChanged += OnPropertyItemValueChanged;
    }

    private void UnhookPropertyChanged(PropertyItem item)
    {
      PropertyItem property = item as PropertyItem;
      if (property == null) return;
      property.ValueChanged -= OnPropertyItemValueChanged;
    }
     
    #endregion

    #region Public API
        
    // TODO: elaborate
    //[Obsolete("This method is to be deprecated in the next version!", false)]
    //public FrameworkElement TryFindEditor(PropertyItem property)
    //{      
    //  CategoryItem category = this.Categories.OfType<CategoryItem>().Where(item => (item.Name == property.CategoryName)).FirstOrDefault();

    //  ItemsControl categoriesList = this.Template.FindName("PART_items", this) as ItemsControl;      
    //  ContentPresenter categoryPresenter = categoriesList.ItemContainerGenerator.ContainerFromItem(category) as ContentPresenter;
    //  Expander categoryExpander = VisualTreeHelper.GetChild(categoryPresenter, 0) as Expander;
      
    //  // Expander should be expanded only if the required editor is found indeed,
    //  // maybe an argument should be expected determining whether to expand the category or not...
    //  if (!categoryExpander.IsExpanded) categoryExpander.IsExpanded = true;

    //  ItemsControl categoryItemsList = categoryExpander.FindName("PART_items") as ItemsControl;
    //  ContentPresenter propertyPresenter = categoryItemsList.ItemContainerGenerator.ContainerFromItem(property) as ContentPresenter;

    //  PropertyContainer propertyContainer = VisualTreeHelper.GetChild(propertyPresenter, 0) as PropertyContainer;
     
    
    //  FrameworkElement grid = VisualTreeHelper.GetChild(propertyPresenter, 0) as FrameworkElement;
    //  ContentControl propertyContent = grid.FindName("PART_content") as ContentControl;
      
    //  ContentPresenter editorPresenter = VisualTreeHelper.GetChild(propertyContent, 0) as ContentPresenter;
    //  FrameworkElement candidate = VisualTreeHelper.GetChild(editorPresenter, 0) as FrameworkElement;
    //  FrameworkElement editor = candidate.FindName("PART_editor") as FrameworkElement;
      
    //  return editor;      
    //}

    #endregion

    #region Internal API

    internal CategoryItem CreateCategory(CategoryAttribute attribute)
    {
      // Check the SelectedObject property to be defined
      Debug.Assert(this.SelectedObject != null);
      if (this.SelectedObject == null) return null;

      // Check the attribute argument to be passed
      Debug.Assert(attribute != null);
      if (attribute == null) return null;

      // Create event arguments for PreviewCategoryCreated event
      CategoryEventArgs categoryArgs = new CategoryEventArgs(PreviewCategoryCreatedEvent)
      {
        Category = attribute,
        Component = this.SelectedObject
      };

      // Raise PreviewCategoryCreated event
      OnPreviewCategoryCreated(categoryArgs);

      // Stop execution and return nothing in case event was cancelled externally or category was removed
      if (categoryArgs.Cancel || categoryArgs.Category == null) return null;

      // Create a new CategoryItem
      CategoryItem categoryItem = new CategoryItem(categoryArgs.Category);

      // Create event arguments for CategoryCreated event
      CategoryItemEventArgs categoryItemArgs = new CategoryItemEventArgs(CategoryCreatedEvent)
      {
        Category = categoryItem,
        Component = this.SelectedObject
      };

      // Raise CategoryCreated event
      OnCategoryCreated(categoryItemArgs);

      // Stop execution and return nothing in case event was cancelled externally or category was removed
      if (categoryItemArgs.Cancel || categoryItemArgs.Category == null) return null;

      // Return resulting item
      return categoryItemArgs.Category;
    }

    internal PropertyItem CreateProperty(PropertyDescriptor descriptor)
    {
      // Check the SelectedObject property to be defined
      Debug.Assert(this.SelectedObject != null);
      if (this.SelectedObject == null) return null;

      // Check the descriptor to be passed
      Debug.Assert(descriptor != null);
      if (descriptor == null) return null;

      // Create event arguments for PreviewPropertyCreated event
      PropertyDescriptorEventArgs descriptorArgs = new PropertyDescriptorEventArgs(PreviewPropertyCreatedEvent)
      {
        Descriptor = descriptor,
        Component = this.SelectedObject
      };

      // Raise PreviewPropertyCreated event
      OnPreviewPropertyCreated(descriptorArgs);

      // Stop execution and return nothing in case event was cancelled externally or descriptor was cleaned up
      if (descriptorArgs.Cancel || descriptorArgs.Descriptor == null) return null;

      // Create a PropertyItem based on descriptor passed
      PropertyItem item = CreatePropertyItem(descriptorArgs.Descriptor);
      if (item == null) return null;

      // Create event arguments for PropertyCreated event
      PropertyItemEventArgs propertyArgs = new PropertyItemEventArgs(PropertyCreatedEvent)
      {
        PropertyItem = item,
        Component = this.SelectedObject
      };

      // Raise PropertyCreated event
      OnPropertyCreated(propertyArgs);

      // Stop execution and return nothing in case event was cancelled externally or property item was cleaned up
      if (propertyArgs.Cancel || propertyArgs.PropertyItem == null) return null;

      // Return resulting item
      return propertyArgs.PropertyItem;
    }

    private PropertyItem CreatePropertyItem(PropertyDescriptor descriptor)
    {
      DependencyPropertyDescriptor dpDescriptor = DependencyPropertyDescriptor.FromProperty(descriptor);
      // Provide additional checks for dependency properties
      if (dpDescriptor != null)
      {
        // Check whether dependency properties are not prohibited
        if (this.PropertyDisplayMode == PropertyDisplayModes.Native) return null;

        // Check whether attached properties are to be displayed
        if (dpDescriptor.IsAttached && !this.ShowAttachedProperties) return null;
      }
      else
      {
        if (this.PropertyDisplayMode == PropertyDisplayModes.Dependency) return null;
      }


      // Check whether readonly properties are to be displayed
      if (descriptor.IsReadOnly && !this.ShowReadOnlyProperties) return null;

      // Check whether property is browsable and add it to the collection
      if (!descriptor.IsBrowsable) return null;

      PropertyItem item = new PropertyItem(this.SelectedObject, descriptor);

      // Evaluate the schema conditions
      if (this.PropertyDisplaySchema != null && !this.PropertyDisplaySchema.Evaluate(item)) return null;

      return item;
    }

    #endregion
  }
}
